<template>
  <div class="pong">
    <div class="header">
      <h2>🏓 Pong 乒乓</h2>
      <div class="stats">
        <div>分数：{{ score }}</div>
        <div>最高：{{ best }}</div>
      </div>
      <div class="actions">
        <button class="btn" @click="newGame">新开一局</button>
        <button class="btn" @click="togglePause">{{ paused ? '继续' : '暂停' }}</button>
        <button class="btn" @click="toggleHelp">玩法说明</button>
      </div>
    </div>

    <div class="canvas-box">
      <canvas ref="canvas" :width="W" :height="H" tabindex="0"></canvas>
      <div v-if="gameOver" class="overlay over">
        <div class="msg">你没接到球，游戏结束</div>
        <button class="btn" @click="newGame">再来一局</button>
      </div>
    </div>

    <div v-if="showHelp" class="help">
      - 玩家控制左侧球拍，按 W/↑ 上移，S/↓ 下移。
      - 将球打过右侧 AI 球拍可得分，未接到球则失败。
      - 可暂停/继续游戏。
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue'

const W = 420
const H = 260
const canvas = ref(null)
let ctx

const score = ref(0)
const best = ref(parseInt(localStorage.getItem('pongBest') || '0'))
const paused = ref(false)
const gameOver = ref(false)
const showHelp = ref(false)

// paddles
const pw = 10, ph = 70
const p1x = 20, p2x = W - pw - 20
let p1y = H / 2 - ph / 2
let p2y = H / 2 - ph / 2
const p1Speed = 4
const aiSpeed = 3

// ball
let bx = W / 2, by = H / 2
let br = 7
let vx = 3, vy = 2.2
let rafId

const keys = new Set()

function newGame() {
  score.value = 0
  gameOver.value = false
  paused.value = false
  p1y = H / 2 - ph / 2
  p2y = H / 2 - ph / 2
  bx = W / 2; by = H / 2
  vx = 3; vy = 2.2
  focusCanvas()
}

function focusCanvas() { nextTick(() => canvas.value && canvas.value.focus()) }
function togglePause() { paused.value = !paused.value }
function toggleHelp() { showHelp.value = !showHelp.value }

function onKeyDown(e) {
  if (['w','W','ArrowUp','s','S','ArrowDown'].includes(e.key)) keys.add(e.key)
}
function onKeyUp(e) {
  keys.delete(e.key)
}

function update() {
  if (paused.value || gameOver.value) return
  // player movement
  if (keys.has('w') || keys.has('W') || keys.has('ArrowUp')) p1y -= p1Speed
  if (keys.has('s') || keys.has('S') || keys.has('ArrowDown')) p1y += p1Speed
  p1y = Math.max(0, Math.min(H - ph, p1y))

  // AI movement: follow ball with limited speed
  const target = by - ph / 2
  if (p2y + ph / 2 < by - 4) p2y += aiSpeed
  else if (p2y + ph / 2 > by + 4) p2y -= aiSpeed
  p2y = Math.max(0, Math.min(H - ph, p2y))

  // ball physics
  bx += vx; by += vy
  // bounce top/bottom
  if (by - br < 0) { by = br; vy *= -1 }
  if (by + br > H) { by = H - br; vy *= -1 }

  // collide with left paddle
  if (bx - br < p1x + pw && bx - br > p1x && by > p1y && by < p1y + ph) {
    bx = p1x + pw + br
    vx = Math.abs(vx) * 1.03
    // angle by hit position
    const offset = (by - (p1y + ph / 2)) / (ph / 2)
    vy = offset * 3.2
  }
  // collide with right paddle
  if (bx + br > p2x && bx + br < p2x + pw && by > p2y && by < p2y + ph) {
    bx = p2x - br
    vx = -Math.abs(vx) * 1.03
    const offset = (by - (p2y + ph / 2)) / (ph / 2)
    vy = offset * 3.2
  }

  // score / miss
  if (bx - br > W) {
    // player scores
    score.value += 1
    if (score.value > best.value) { best.value = score.value; localStorage.setItem('pongBest', String(best.value)) }
    resetBall(-1)
  }
  if (bx + br < 0) {
    // player misses
    gameOver.value = true
  }
}

function resetBall(dir = 1) {
  bx = W / 2; by = H / 2
  vx = 3 * dir; vy = (Math.random() * 2 - 1) * 2.5
}

function draw() {
  if (!ctx) return
  // bg
  ctx.fillStyle = '#f0f4f8'
  ctx.fillRect(0, 0, W, H)

  // center line
  ctx.strokeStyle = '#cbd5e1'
  ctx.setLineDash([6, 6])
  ctx.beginPath()
  ctx.moveTo(W / 2, 0)
  ctx.lineTo(W / 2, H)
  ctx.stroke()
  ctx.setLineDash([])

  // paddles
  ctx.fillStyle = '#1e293b'
  ctx.fillRect(p1x, p1y, pw, ph)
  ctx.fillStyle = '#64748b'
  ctx.fillRect(p2x, p2y, pw, ph)

  // ball
  ctx.beginPath()
  ctx.arc(bx, by, br, 0, Math.PI * 2)
  ctx.fillStyle = '#ef4444'
  ctx.fill()
  ctx.closePath()

  // score
  ctx.fillStyle = '#1e293b'
  ctx.font = 'bold 16px system-ui'
  ctx.fillText('分数: ' + score.value, 10, 20)
}

function loop() {
  update()
  draw()
  rafId = requestAnimationFrame(loop)
}

onMounted(() => {
  ctx = canvas.value.getContext('2d')
  newGame()
  rafId = requestAnimationFrame(loop)
  window.addEventListener('keydown', onKeyDown)
  window.addEventListener('keyup', onKeyUp)
})

onBeforeUnmount(() => {
  cancelAnimationFrame(rafId)
  window.removeEventListener('keydown', onKeyDown)
  window.removeEventListener('keyup', onKeyUp)
})
</script>

<style scoped>
.pong { max-width: 480px; margin: 16px auto; font-family: system-ui, -apple-system, Segoe UI, PingFang SC, Microsoft YaHei; }
.header { display:flex; align-items:center; justify-content:space-between; gap:8px; }
.stats { display:flex; gap:12px; }
.actions { display:flex; gap:8px; }
.btn { padding:6px 10px; border:none; border-radius:6px; background:#0ea5e9; color:#fff; cursor:pointer; }
.btn:hover { filter:brightness(1.05); }
.canvas-box { position:relative; margin-top:12px; border-radius:8px; overflow:hidden; box-shadow: 0 4px 12px rgba(0,0,0,.15); }
canvas { display:block; background:#f0f4f8; border-radius:8px; outline:none; }
.overlay { position:absolute; inset:0; display:flex; align-items:center; justify-content:center; flex-direction:column; gap:10px; background:rgba(255,255,255,.8); text-align:center; }
.help { margin-top:10px; background:#f8f6f2; border:1px solid #eee; border-radius:8px; padding:8px 10px; color:#3a4a5a; font-size:14px; }
</style>